import json
import time
import functools
from json import decoder, dumps
from typing import Any, Dict, Optional, Union
import requests
from requests.adapters import HTTPAdapter
from utils.config_tools import Config
from commons.logger import log

IMG = ['jpg', 'jpeg', 'gif', 'bmp', 'webp', 'xlsx', 'png']


def request(func):
    """
    装饰器函数，用于包装HTTP请求相关的函数，添加请求和响应的日志记录、处理响应数据等功能。
    参数：
    - func: 被装饰的函数，应该是用于发起 HTTP 请求的函数，期望接收相关的请求参数（如路径、参数、请求头等）并返回 requests.Response 对象。

    返回值：
    - 返回包装后的函数，该函数在执行原请求函数的基础上，会添加日志记录和响应数据处理等功能。
    """

    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):  # 这里添加self参数，因为被装饰的是实例方法
        func_name = func.__name__
        log.info('-------------- Request -----------------[🚀]')

        # 通过self获取config_name，因为self指向HttpRequest实例，其保存着初始化传入的参数
        config_name = self.config_name
        if config_name is None:
            log.error("配置文件名称参数不能为空，请确保传入正确的配置文件名称。")
            raise ValueError("配置文件名称参数不能为空")

        # 获取path参数，优先从位置参数中获取，若不存在则从关键字参数中获取，设置合理默认值
        path = kwargs.get('path', '')
        if not args:
            path = kwargs.get('path', '')
        else:
            if len(args) > 0:
                path = args[0]
            else:
                log.error("path参数不能为空，请确保传入正确的请求路径参数，格式应为符合URL规范的字符串。")
                raise ValueError("path参数不能为空")

        # 初始化HttpRequest对象，传入配置文件名称（这里其实已经通过self获取到实例本身了）
        http_request = self
        url = path
        if 'http' not in url:
            url = http_request.base_url + path

        # 判断是否为图片文件类型（优化判断逻辑，使其更严谨）
        img_file = False
        file_extension = url.split('.')[-1].lower() if '.' in url else ""
        if file_extension in IMG:
            img_file = True

        log.info(f'[method]: {func_name.upper()}     [url]: {url} \n')

        # 获取其他可选请求参数，设置合理默认值并进行日志记录
        auth = kwargs.get('auth', None)
        headers = kwargs.get('headers', None)
        cookies = kwargs.get('cookies', None)
        params = kwargs.get('params', None)
        data = kwargs.get('data', None)
        json_data = kwargs.get('json', None)

        # 记录认证信息（auth），注意对敏感信息进行脱敏处理（示例简单将值替换为******，实际需根据具体情况合理脱敏）
        if auth:
            desensitized_auth = {}
            for key, value in auth.items():
                if "password" in key.lower():  # 假设包含password字段为敏感信息，进行脱敏
                    desensitized_auth[key] = "******"
                else:
                    desensitized_auth[key] = value
            log.debug(f'[auth: {json.dumps(desensitized_auth, ensure_ascii=False, indent=2)} (已对敏感信息脱敏)]')

        # 记录请求头信息（headers），部分关键的headers信息可考虑用INFO级别记录（示例判断Content-Type，可按需调整）
        if headers:
            if isinstance(headers, dict):
                content_type = headers.get("Content-Type", "")
                if content_type:
                    log.info(f'[headers: Content-Type: {content_type} (关键请求头信息)]')
                log.debug(f'[headers: {json.dumps(headers, ensure_ascii=False, indent=2)} (完整请求头信息)]')

        # 记录Cookies信息
        if cookies:
            log.debug(f'[cookies: {json.dumps(cookies, ensure_ascii=False, indent=2)}]')

        # 记录请求参数（params），封装json序列化逻辑到单独函数提高复用性（这里简单内联展示）
        if params:
            req_params = _serialize_and_log_data(params, "params")
            log.debug(f'[params: {req_params}]')

        # 记录data信息，对可能出现的TypeError异常进行更详细的处理和日志记录
        if data:
            try:
                req_data = _serialize_and_log_data(data, "data")
                log.debug(f'[data: {req_data}]')
            except TypeError as e:
                log.error(f'[data: 无法序列化data参数，原因可能是数据类型不符合预期（{type(data)}），原始数据: {data}]')

        # 记录json_data信息
        if json_data:
            req_json = _serialize_and_log_data(json_data, "json_data")
            log.debug(f'[json: {req_json}]')

        # 记录请求发起的时间戳，用于计算请求总耗时
        request_start_time = time.time()

        # 发送http请求
        try:
            r = func(self, *args, **kwargs)  # 调用原函数时传入self
        except requests.RequestException as e:
            log.error(f"请求 {func_name} 出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}")
            # 可根据具体情况进行重试等处理，这里简单重新抛出异常
            raise

        ResponseResult.status_code = r.status_code
        log.info('-------------- Response ----------------[🛬️]')
        if ResponseResult.status_code == 200 or ResponseResult.status_code == 304:
            log.info(
                f'successful with status {str(ResponseResult.status_code)} \n')
            # 如果响应是JSON格式，记录一些关键信息（示例，假设响应中有"data"字段比较重要）
            try:
                response_data = r.json()
                if "data" in response_data:
                    log.info(f"响应中的关键信息（示例）: [data: {response_data['data']}]")
            except requests.exceptions.JSONDecodeError:
                log.warning("响应数据无法解析为JSON格式，无法获取关键信息。")
        else:
            log.warning(
                f'unsuccessful with status {str(ResponseResult.status_code)} \n')
            # 记录服务器返回的错误信息（如果有），方便排查问题
            if r.text:
                log.warning(f"服务器返回的错误信息（示例）: {r.text}")

        resp_time = r.elapsed.total_seconds()
        request_total_time = time.time() - request_start_time  # 计算请求总耗时
        log.info(f"请求总耗时: {request_total_time} 秒，响应耗时: {resp_time} 秒")

        # 处理响应数据，提取到单独函数增强可读性和可维护性
        _handle_response_data(r, img_file, resp_time)

        return r

    return wrapper


def _serialize_and_log_data(data, parameter_name):
    """
    对给定的数据进行JSON序列化，并记录相关日志信息。

    参数:
    - data: 需要序列化的数据，可以是字典等可序列化类型。
    - parameter_name: 参数名称，用于在日志中标识是哪个参数的数据。

    返回:
    - 序列化后的字符串数据，如果序列化成功。
    """
    try:
        serialized_data = json.dumps(
            data,
            ensure_ascii=False,
            sort_keys=True,
            indent=2,
        )
        log.debug(f'[{parameter_name}: {serialized_data}]')
        return serialized_data
    except TypeError as e:
        log.error(f'[{parameter_name}: 无法序列化该参数数据，出现TypeError异常，原因: {e}]')
        raise


def _handle_response_data(r: requests.Response, img_file: bool,
                          resp_time: float) -> None:
    """
    根据响应的不同情况（如JSON格式、文本、图片等）进行相应的处理，并记录相关信息到日志中。

    参数:
    - r: requests.Response对象，包含了服务器返回的响应信息。
    - img_file: 布尔值，表示响应是否为图片类型，通过一定的判断逻辑预先确定。
    - resp_time: 本次请求响应所花费的时间，单位为秒。

    该函数主要职责是对响应数据进行合理处理，根据其类型（JSON、文本、图片）分别进行不同的操作，
    包括解析JSON数据、记录相应的日志信息以及将处理后的响应数据存储到ResponseResult类的response属性中。
    """
    try:
        # 尝试将响应解析为JSON格式数据
        parsed_json_response = r.json()
        log.debug(f'解析数据为json格式花费时间: {resp_time} s')
        # 将解析后的JSON数据格式化输出并记录日志，方便查看详细结构
        resp_json = json.dumps(
            parsed_json_response,
            ensure_ascii=False,
            sort_keys=True,
            indent=2,
        )
        log.debug(f'解析后的JSON数据为：\n {resp_json} \n')

        # 假设业务上期望JSON数据中有某个关键字段名为"code"，这里可以添加简单验证逻辑（示例）
        # if "code" in parsed_json_response:
        #     log.info(f'[JSON Key "result_code" Found: {parsed_json_response["result_code"]}]')
        # else:
        #     log.warning(f'[JSON Key "result_code" Not Found in Response]')

        # 将解析后的JSON数据存储到ResponseResult.response中，方便后续使用
        ResponseResult.response = parsed_json_response
    except requests.exceptions.JSONDecodeError as msg:
        log.warning(f'[Warning: Failed to Convert Response to JSON. Error: {msg}]')
        if img_file:
            content_type = r.headers.get("Content-Type", "")
            log.debug(f'[Response Type: {content_type} [Time Taken: {resp_time} seconds]')
            # 对于图片类型响应，直接将响应内容（字节数据）存储到ResponseResult.response中
            # 这里可以添加一些额外验证逻辑，比如检查图片数据是否完整（示例）
            if len(r.content) > 0:
                ResponseResult.response = r.content
            else:
                log.error(f'[Error: Empty Image Data Received]')
        else:
            log.debug(f'[Response Type: text] [Time Taken: {resp_time} seconds]\n')
            text_response = r.text
            log.debug(f'[Response Data (Text):]\n {text_response} \n')
            # 对于文本类型响应，添加文本长度信息到日志记录中（示例）
            log.info(f'[Text Response Length: {len(text_response)} characters]')
            # 将文本数据存储到ResponseResult.response中
            ResponseResult.response = text_response
    except requests.RequestException as req_error:
        log.error(f'[Error: Request Exception Occurred While Handling Response. Error: {req_error}]')


class ResponseResult:
    status_code = 200
    response = None


class HttpRequest:
    def __init__(self, config_name, u_token=None):
        """
        初始化HttpRequest对象，接收配置文件名称以及可选的token参数。
        根据传入的配置文件名称初始化Config类来获取基础URL等配置信息，
        并可以传入一个token用于后续请求中自动添加到URL中（如果有需要）。

        参数:
        :param config_name: 配置文件名称，用于初始化Config类来读取相关配置。
        :param u_token: 可选参数，用于后续请求中添加到URL的token字符串（默认为None）。
        """
        self.config_name = config_name
        self.token = u_token
        self._init_base_url()  # 调用方法来初始化基础URL

    def _init_base_url(self):
        """
        私有方法，用于初始化基础URL。
        通过Config类读取配置文件获取基础URL，添加错误处理及考虑性能优化（示例简单处理，可完善缓存机制等）。
        """
        try:
            config = Config(self.config_name)  # 根据传入的配置文件名称初始化Config类
            self._cached_base_url = config.read_config_ini('envHost', 'HOST')
            log.info(f"成功从配置文件读取基础URL: {self._cached_base_url}")
            self.base_url = self._cached_base_url  # 将获取到的基础URL赋值给实际使用的base_url属性
        except FileNotFoundError:
            log.error("配置文件不存在，请检查配置文件路径是否正确")
            raise
        except KeyError:
            log.error("配置文件中对应节点不存在，请检查配置文件中相关节点配置是否正确")
            raise
        except Exception as e:
            log.error(f"读取配置文件获取基础URL时出现其他错误，错误信息: {e}")
            raise

    def _build_url(self, path):
        """
        私有方法，用于根据传入的路径构建完整的请求URL。
        如果路径本身不包含协议（http或https），则拼接上类中配置的基础URL，并根据是否有token添加相应的查询参数。

        参数:
        - path: 请求的路径字符串，通常是接口的相对路径部分。

        返回值:
        - 构建好的完整请求URL字符串。
        """
        url = path
        token_query = "" if not self.token else "?token=" + self.token
        if 'http' not in url:
            url = self.base_url + path + token_query
        return url

    def _validate_params(self, params):
        """
        简单验证params参数是否符合要求（示例，可完善更严格的校验）。
        判断参数是否为可迭代类型（字典、列表、元组），可进一步完善验证逻辑。

        参数:
        :param params: 请求参数字典。

        返回值:
        :return: 如果参数合法返回True，否则返回False。
        """
        return isinstance(params, (dict, list, tuple))  # 简单判断是否为可迭代类型，可进一步完善

    def _validate_json(self, json_data):
        """
        简单验证json参数是否可序列化（示例，可使用更严格的JSON校验库等完善）。
        尝试使用json.dumps将数据转换为JSON字符串，若成功则表示可序列化，返回True，否则返回False。
        参数:
        :param json_data: JSON格式数据字典。

        返回值:
        :return: 如果可序列化返回True，否则返回False。
        """
        try:
            json.dumps(json_data)
            return True
        except:
            return False

    @request
    def get(self, path, params=None, headers=None, **kwargs):
        """
        发起GET类型的HTTP请求。

        参数:
        - path: 请求的路径字符串，相对路径或者包含完整协议的URL。
        - params: 可选的请求参数字典，用于GET请求中传递查询参数，默认为None。
        - headers: 可选的请求头信息字典，用于设置请求的头部信息，默认为None。
        - **kwargs: 其他可选的关键字参数，可用于传递如认证信息等额外的请求相关参数。

        返回值:
        - requests.Response对象，包含服务器返回的响应信息。

        日志记录:
        - 在发起请求前记录请求的相关参数信息（包括路径、参数、请求头等），请求完成后记录响应的状态码以及基本的成功/失败信息，
        - 针对GET请求，额外记录请求的参数情况（如果有），方便排查问题时了解具体的请求内容。
        """
        url = self._build_url(path)
        log.info(f"发起GET请求，请求URL: {url}")
        if params:
            log.debug(f"GET请求携带的参数: {params}")
        try:
            response = self.session.get(url, params=params, headers=headers, **kwargs)
            return response
        except requests.RequestException as e:
            log.error(f"GET请求出现异常，异常类型: {e.__class__.__name__}，异常信息: {str(e)}。该异常可能导致无法获取到期望的数据，请检查网络连接、接口是否可用等情况。")
            raise